// Copyright 2011, Vanya Davidenko.
// Используемая кодировка: UTF-8.

#ifndef LIBV_FILE_LINUX_BASIC_FILE_IMPL_HPP_
#define LIBV_FILE_LINUX_BASIC_FILE_IMPL_HPP_

#ifndef __linux__
# error "This header should be used in Linux only."
#endif

#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <linux/falloc.h>

#include <string>
#include <libv/file/basic_file.h>



namespace libv
{

FileHandle BasicFile::BasicOpenImpl(const ::std::string& file_path,
                                    const int open_type,
                                    const bool enable_read_ahead_caching,
                                    const int additional_flags,
                                    const bool read_only) const
{
    int flags = additional_flags | O_LARGEFILE;
    flags |= read_only ? O_RDONLY : O_RDWR;

    if ( open_type == OpenType::kCreateIfNotExist )
        flags |= ( O_CREAT | O_EXCL );
    else if ( open_type == OpenType::kOpenAlways )
        flags |= O_CREAT;

    const int file_handle = ::open(file_path.c_str(), flags,
                                   S_IRUSR | S_IWUSR | S_IRGRP);
    last_error_ = errno;

    // Есть несколько режимов кэширования:
    // обычный (он по умолчанию)
    // последовательное чтение (читать в 2 раза больше блоков вперед)
    // произвольное чтение (отключает упреждающее чтение).
    // Другие не важны, так как в терминах windows их не задать.
    if ( file_handle != LIBV_FILE_INVALID_HANDLE && !enable_read_ahead_caching )
    {
        const int r = ::posix_fadvise(file_handle, 0,Size(), POSIX_FADV_RANDOM);
        last_error_ = errno;
        if ( r == -1 )
        {
            // last_error_ не должен перезаписываться errno после close.
            ::close(file_handle);
            return LIBV_FILE_INVALID_HANDLE;
        }
    }
    return file_handle;
}



bool BasicFile::CloseImpl() const
{
    const bool result = ( ::close(file_handle_) == 0 );
    last_error_ = errno;
    return result;
}



fpos64 BasicFile::SizeImpl() const
{
    const off64_t result = ::lseek64(file_handle_, 0, SEEK_END);
    last_error_ = errno;
    return result;
}



bool BasicFile::ResizeImpl(const fpos64 size)
{
    /* В linux есть несколько способов преаллокации:
     *  - ручной через write (наиболее неоптимально)
     *  - posix_fallocate, в зависимости от реализации и файловой системы
     *    реализующийся или как явная запись нулей, или изменением служебных
     *    структур данных ф.с.
     *  - новый (2009 год) вызов fallocate, работающий на последних ф.с.,
     *    например, на ext4 мгновенно резервирующий блоки на диске.
     * В будущем рекомендуется пользоваться posix_fallocate, так как в
     * зависимости от доступности на системе fallocate он будет определять
     * алгоритм. */

    // fallocate только увеличивает размер
    if ( size <= file_size_ )
    {
        const bool result = ( ::ftruncate(file_handle_, size) == 0 );
        last_error_ = errno;
        return result;
    }

    int r = 0;
#if defined(SYS_fallocate)  // из linux/falloc.h
    const int resize_mode = 0;
    const loff_t loff(file_size_);
    const loff_t llen(size - file_size_);

    r = ::syscall(SYS_fallocate, file_handle_, resize_mode, loff, llen);
    last_error_ = errno;

    if ( r == 0 )
        return true;

    // ФС поддерживает fallocate, но произошла ошибка во время его выполнения
    else if ( errno != EOPNOTSUPP && errno != ENOSYS )
        return false;
#endif

    const off_t off(file_size_);
    const off_t len(size - file_size_);
    r = ::posix_fallocate(file_handle_, off, len);
    last_error_ = r;

    return r == 0;
}



bool BasicFile::FlushImpl()
{
    const bool result = ( ::fdatasync(file_handle_) == 0 );
    last_error_ = errno;
    return result;
}



bool BasicFile::BasicLockImpl(const int lock_type, const bool block) const
{
    int flags = ( lock_type == LockType::kExclusiveLock ) ? LOCK_EX : LOCK_SH;

    if ( !block )
        flags |= LOCK_NB;

    // В Linux нельзя иметь одновременно 2 блокировки на файл,
    // вызов новой преобразует текущую
    const bool result = ( ::flock(file_handle_, flags) == 0 );
    last_error_ = errno;

    if ( !result && !block && errno == EWOULDBLOCK )
        last_error_ = LIBV_FILE_NO_ERROR;

    return result;
}



bool BasicFile::UnlockImpl() const
{
    const bool result = ( ::flock(file_handle_, LOCK_UN) == 0 );
    last_error_ = errno;
    return result;
}

}  // libv

#endif  // LIBV_FILE_LINUX_BASIC_FILE_IMPL_HPP_
